feat: honest peer resolution and relative-to-industry confidence - #13
Merged
Conversation
Fix a section that reported "Not available ... Low confidence (0.37, 0% field coverage)" for any company outside a curated peer group -- KPIT and the entire automotive ER&D cohort were in none. With no peers, relative.py computed nothing and evidence.py then scored 0.80 x (0.4 + 0.6x0) = 0.32 plus a +0.05 cross-source agreement bonus awarded over zero rows, so the module claimed two sources corroborated each other while holding no data. That 0.37 leaked into the report-level and thesis-level aggregates as a plain mean. Zero coverage now collapses to 0.00 with a reason that says why, the agreement bonus is gated on there being something to agree on, and ev.aggregate blends by a coverage-weighted mean so an empty module carries no weight. The same arithmetic was scoring 0.37 for every unknown Buffett criterion; those reprice to 0.00 as well. Add an auto_erd peer group (KPITTECH, TATAELXSI, TATATECH, LTTS, CYIENT) plus auto_components, hospitals_diagnostics and capital_goods_defence, and a resolution ladder in peers.py: curated membership, then a keyword match on Yahoo's industry, then Finnhub, then none. The resulting PeerBasis travels on PeerComparison, RelativeComparison and IndustryIntelligence and discounts confidence via a new evidence.confidence(reliability_factor=...), so a guessed cohort can never read like a deliberate one and a rank among five hand-picked names can never reach the High tier. A matched group now reframes the industry narrative too -- KPIT's sub-domains become SDV, ADAS and EV powertrain rather than "IT services & outsourcing" -- while Yahoo's raw industry string is kept verbatim alongside, since hiding the disagreement would be worse than showing it. Extend the metric set to EV/EBITDA, ROA and P/S (7 -> 10), measuring coverage against the metrics the peer set can actually rank on so that adding a metric Indian peers rarely report does not silently mark every Indian company down. RelativeMetric now carries its unit, because both renderers inferred it from the metric name and would have printed EV/EBITDA as 3000%. Repair four dead tickers that each silently dropped a company out of its own peer table: TATAMOTORS.NS (superseded by the TMCV/TMPV demerger, both already listed), SPICEJET.NS (BSE only now), and LTIM.NS/AKZOINDIA.NS (unresolvable across .NS and .BO, removed rather than guessed at). No offline test can catch this, so scripts/validate_peers.py checks the file against the provider on demand. Ships per-group provenance and docs/confidence.md covering the formula's reasoning and its limits.
This was referenced Jul 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the reported "3. Relative to Industry — Not available. No curated peer group matched, and the relative module returned zero metrics with Low confidence (0.37, 0% field coverage)".
The bug
peers._group_formatches by exact ticker membership. KPIT was in no group — and neither were Tata Elxsi, Tata Technologies, LTTS or Cyient; the whole automotive ER&D cohort was absent from the repo. With no peers,relative.pyskipped all 7 metrics, andevidence.pythen computed:That reason claims two sources cross-checked each other over zero rows. The 0.37 wasn't a low-confidence answer, it was a confident-looking number manufactured from nothing — and it leaked into both
report.py's andthesis.py's aggregates as a plain mean. A plausible number from no data is worse than a zero, because a zero is obviously a zero.Deliberate, and the main thing to review:
unknownBuffett criterion (buffett.py:295)The Buffett path was hit by the identical arithmetic. Report-level confidence will move for affected tickers — upward, since an empty module no longer drags the mean down.
The fix
0.4floor exists so a couple of gaps don't collapse a score; it no longer applies when nothing was computed. The agreement bonus is gated on there being something to agree on.PeerBasistravels with the comparison and discounts confidence through a newreliability_factor, so a guessed cohort can't read like a deliberate one, and a rank among five hand-picked names can never reach the High tier (curated caps at 0.90 by construction).auto_erd+auto_components,hospitals_diagnostics,capital_goods_defence.RelativeMetric.unitstops renderers guessing by name and printing EV/EBITDA as3000%.Dead tickers found en route
Each one silently dropped a company out of its own peer table — the reported symptom, arriving by a different route. Found by
scripts/validate_peers.py, added here because no offline test can catch it:TATAMOTORS.NS— superseded by the TMCV/TMPV demerger; both were already in the groupSPICEJET.NS→SPICEJET.BO(NSE line gone from the provider)LTIM.NS,AKZOINDIA.NS— unresolvable across.NSand.BO; removed rather than guessed atVerification
ruff+mypyclean; 142 tests pass (103 → 142).test_peers.pywas making a live network call on the unknown-ticker path; it's now monkeypatched.Live, on the actual reported case:
Reliance still resolves to
oil_gas_energyand Eicher toauto_oem(both are double-booked across groups; a test now pins the first-match-wins order, since appending vs inserting inpeers.yamlsilently reframes companies).